{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# LangGraph 에 자주 등장하는 Python 문법"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## TypedDict\n",
    "\n",
    "`dict`와 `TypedDict`의 차이점과 `TypedDict`가 왜 `dict` 대신 사용되는지 설명해드리겠습니다.\n",
    "\n",
    "1. `dict`와 `TypedDict`의 주요 차이점:\n",
    "\n",
    "   a) 타입 검사:\n",
    "      - `dict`: 런타임에 타입 검사를 하지 않습니다.\n",
    "      - `TypedDict`: 정적 타입 검사를 제공합니다. 즉, 코드 작성 시 IDE나 타입 체커가 오류를 미리 잡아낼 수 있습니다.\n",
    "\n",
    "   b) 키와 값의 타입:\n",
    "      - `dict`: 키와 값의 타입을 일반적으로 지정합니다 (예: Dict[str, str]).\n",
    "      - `TypedDict`: 각 키에 대해 구체적인 타입을 지정할 수 있습니다.\n",
    "\n",
    "   c) 유연성:\n",
    "      - `dict`: 런타임에 키를 추가하거나 제거할 수 있습니다.\n",
    "      - `TypedDict`: 정의된 구조를 따라야 합니다. 추가적인 키는 타입 오류를 발생시킵니다.\n",
    "\n",
    "2. `TypedDict`가 `dict` 대신 사용되는 이유:\n",
    "\n",
    "   a) 타입 안정성: \n",
    "      `TypedDict`는 더 엄격한 타입 검사를 제공하여 잠재적인 버그를 미리 방지할 수 있습니다.\n",
    "\n",
    "   b) 코드 가독성:\n",
    "      `TypedDict`를 사용하면 딕셔너리의 구조를 명확하게 정의할 수 있어 코드의 가독성이 향상됩니다.\n",
    "\n",
    "   c) IDE 지원:\n",
    "      `TypedDict`를 사용하면 IDE에서 자동 완성 및 타입 힌트를 더 정확하게 제공받을 수 있습니다.\n",
    "\n",
    "   d) 문서화:\n",
    "      `TypedDict`는 코드 자체가 문서의 역할을 하여 딕셔너리의 구조를 명확히 보여줍니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TypedDict와 Dict의 차이점 예시\n",
    "from typing import Dict, TypedDict\n",
    "\n",
    "# 일반적인 파이썬 딕셔너리(dict) 사용\n",
    "sample_dict: Dict[str, str] = {\n",
    "    \"name\": \"테디\",\n",
    "    \"age\": \"30\",  # 문자열로 저장 (dict 에서는 가능)\n",
    "    \"job\": \"개발자\",\n",
    "}\n",
    "\n",
    "\n",
    "# TypedDict 사용\n",
    "class Person(TypedDict):\n",
    "    name: str\n",
    "    age: int  # 정수형으로 명시\n",
    "    job: str\n",
    "\n",
    "\n",
    "typed_dict: Person = {\"name\": \"셜리\", \"age\": 25, \"job\": \"디자이너\"}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# dict의 경우\n",
    "sample_dict[\"age\"] = 35  # 문자열에서 정수로 변경되어도 오류 없음\n",
    "sample_dict[\"new_field\"] = \"추가 정보\"  # 새로운 필드 추가 가능\n",
    "\n",
    "# TypedDict의 경우\n",
    "typed_dict[\"age\"] = 35  # 정수형으로 올바르게 사용\n",
    "typed_dict[\"age\"] = \"35\"  # 타입 체커가 오류를 감지함\n",
    "typed_dict[\"new_field\"] = (\n",
    "    \"추가 정보\"  # 타입 체커가 정의되지 않은 키라고 오류를 발생시킴\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "하지만 TypedDict의 진정한 가치는 정적 타입 검사기를 사용할 때 드러납니다. \n",
    "\n",
    "예를 들어, mypy와 같은 정적 타입 검사기를 사용하거나 PyCharm, VS Code 등의 IDE에서 타입 검사 기능을 활성화하면, 이러한 타입 불일치와 정의되지 않은 키 추가를 오류로 표시합니다.\n",
    "정적 타입 검사기를 사용하면 다음과 같은 오류 메시지를 볼 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Annotated\n",
    "\n",
    "이 문법은 타입 힌트에 메타데이터를 추가할 수 있게 해줍니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`Annotated`를 사용하는 주요 이유\n",
    "\n",
    "**추가 정보 제공(타입 힌트) / 문서화** \n",
    "\n",
    "- 타입 힌트에 추가적인 정보를 포함시킬 수 있습니다. 이는 코드를 읽는 사람이나 도구에 더 많은 컨텍스트를 제공합니다.\n",
    "- 코드에 대한 추가 설명을 타입 힌트에 직접 포함시킬 수 있습니다.\n",
    "\n",
    "`name: Annotated[str, \"이름\"]`\n",
    "\n",
    "`age: Annotated[int, \"나이\"]`\n",
    "\n",
    "----"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`Annotated` 는 Python의 typing 모듈에서 제공하는 특별한 타입 힌트로, 기존 타입에 메타데이터를 추가할 수 있게 해줍니다.\n",
    "\n",
    "`Annotated` 는 타입 힌트에 추가 정보를 포함시킬 수 있는 기능을 제공합니다. 이를 통해 코드의 가독성을 높이고, 더 자세한 타입 정보를 제공할 수 있습니다.\n",
    "\n",
    "\n",
    "### Annotated 주요 기능(사용 이유)\n",
    "\n",
    "1. **추가 정보 제공**: 타입 힌트에 메타데이터를 추가하여 더 상세한 정보를 제공합니다.\n",
    "2. **문서화**: 코드 자체에 추가 설명을 포함시켜 문서화 효과를 얻을 수 있습니다.\n",
    "3. **유효성 검사**: 특정 라이브러리(예: Pydantic)와 함께 사용하여 데이터 유효성 검사를 수행할 수 있습니다.\n",
    "4. **프레임워크 지원**: 일부 프레임워크(예: LangGraph)에서는 `Annotated`를 사용하여 특별한 동작을 정의합니다.\n",
    "\n",
    "**기본 문법**\n",
    "\n",
    "- `Type`: 기본 타입 (예: `int`, `str`, `List[str]` 등)\n",
    "- `metadata1`, `metadata2`, ...: 추가하고자 하는 메타데이터\n",
    "  \n",
    "```python\n",
    "from typing import Annotated\n",
    "\n",
    "variable: Annotated[Type, metadata1, metadata2, ...]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 사용 예시"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "기본 사용"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated\n",
    "\n",
    "name: Annotated[str, \"사용자 이름\"]\n",
    "age: Annotated[int, \"사용자 나이 (0-150)\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pydantic과 함께 사용"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated, List\n",
    "from pydantic import Field, BaseModel, ValidationError\n",
    "\n",
    "\n",
    "class Employee(BaseModel):\n",
    "    id: Annotated[int, Field(..., description=\"직원 ID\")]\n",
    "    name: Annotated[str, Field(..., min_length=3, max_length=50, description=\"이름\")]\n",
    "    age: Annotated[int, Field(gt=18, lt=65, description=\"나이 (19-64세)\")]\n",
    "    salary: Annotated[\n",
    "        float, Field(gt=0, lt=10000, description=\"연봉 (단위: 만원, 최대 10억)\")\n",
    "    ]\n",
    "    skills: Annotated[\n",
    "        List[str], Field(min_items=1, max_items=10, description=\"보유 기술 (1-10개)\")\n",
    "    ]\n",
    "\n",
    "\n",
    "# 유효한 데이터로 인스턴스 생성\n",
    "try:\n",
    "    valid_employee = Employee(\n",
    "        id=1, name=\"테디노트\", age=30, salary=1000, skills=[\"Python\", \"LangChain\"]\n",
    "    )\n",
    "    print(\"유효한 직원 데이터:\", valid_employee)\n",
    "except ValidationError as e:\n",
    "    print(\"유효성 검사 오류:\", e)\n",
    "\n",
    "# 유효하지 않은 데이터로 인스턴스 생성 시도\n",
    "try:\n",
    "    invalid_employee = Employee(\n",
    "        name=\"테디\",  # 이름이 너무 짧음\n",
    "        age=17,  # 나이가 범위를 벗어남\n",
    "        salary=20000,  # 급여가 범위를 벗어남\n",
    "        skills=\"Python\",  # 리스트가 아님\n",
    "    )\n",
    "except ValidationError as e:\n",
    "    print(\"유효성 검사 오류:\")\n",
    "    for error in e.errors():\n",
    "        print(f\"- {error['loc'][0]}: {error['msg']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### LangGraph에서의 사용(add_messages)\n",
    "\n",
    "`add_messages` 는 LangGraph 에서 메시지를 리스트에 추가하는 함수입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated, TypedDict\n",
    "from langgraph.graph import add_messages\n",
    "\n",
    "\n",
    "class MyData(TypedDict):\n",
    "    messages: Annotated[list, add_messages]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated, TypedDict\n",
    "from langgraph.graph import add_messages\n",
    "\n",
    "\n",
    "class MyData(TypedDict):\n",
    "    messages: Annotated[list, add_messages]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**참고**\n",
    "\n",
    "1. `Annotated`는 Python 3.9 이상에서 사용 가능합니다.\n",
    "2. 런타임에는 `Annotated`가 무시되므로, 실제 동작에는 영향을 주지 않습니다.\n",
    "3. 타입 검사 도구나 IDE가 `Annotated`를 지원해야 그 효과를 볼 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## add_messages\n",
    "\n",
    "`messages` 키는 [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages) 리듀서 함수로 주석이 달려 있으며, 이는 LangGraph에게 기존 목록에 새 메시지를 추가하도록 지시합니다. \n",
    "\n",
    "주석이 없는 상태 키는 각 업데이트에 의해 덮어쓰여져 가장 최근의 값이 저장됩니다. \n",
    "\n",
    "`add_messages` 함수는 2개의 인자(left, right)를 받으며 좌, 우 메시지를 병합하는 방식으로 동작합니다.\n",
    "\n",
    "**주요 기능**\n",
    "   - 두 개의 메시지 리스트를 병합합니다.\n",
    "   - 기본적으로 \"append-only\" 상태를 유지합니다.\n",
    "   - 동일한 ID를 가진 메시지가 있을 경우, 새 메시지로 기존 메시지를 대체합니다.\n",
    "\n",
    "**동작 방식**\n",
    "\n",
    "   - `right`의 메시지 중 `left`에 동일한 ID를 가진 메시지가 있으면, `right`의 메시지로 대체됩니다.\n",
    "   - 그 외의 경우 `right`의 메시지가 `left`에 추가됩니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "   - `left` (Messages): 기본 메시지 리스트\n",
    "   - `right` (Messages): 병합할 메시지 리스트 또는 단일 메시지\n",
    "\n",
    "**반환값**\n",
    "\n",
    "   - `Messages`: `right`의 메시지들이 `left`에 병합된 새로운 메시지 리스트"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import AIMessage, HumanMessage\n",
    "from langgraph.graph import add_messages\n",
    "\n",
    "# 기본 사용 예시\n",
    "msgs1 = [HumanMessage(content=\"안녕하세요?\", id=\"1\")]\n",
    "msgs2 = [AIMessage(content=\"반갑습니다~\", id=\"2\")]\n",
    "\n",
    "result1 = add_messages(msgs1, msgs2)\n",
    "print(result1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "동일한 ID 를 가진 Message 가 있을 경우 대체됩니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 동일한 ID를 가진 메시지 대체 예시\n",
    "msgs1 = [HumanMessage(content=\"안녕하세요?\", id=\"1\")]\n",
    "msgs2 = [HumanMessage(content=\"반갑습니다~\", id=\"1\")]\n",
    "\n",
    "result2 = add_messages(msgs1, msgs2)\n",
    "print(result2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
